package My

import (
	"encoding/json"
	"errors"
	"fmt"
	"db2s/ETL"
	"db2s/global"
	"strconv"
	"strings"
)

// IfTablePartitionSum task 服务会用到
func (my TableInfoMeta) IfTablePartitionSum(s global.TablesMetaInfoInput) (partitionData global.PartitionSumReturnResult, err error) {
	var (
		event = "[MySQLIfTablePartitionSum]"
		res   any
	)
	strSql := fmt.Sprintf("SELECT (select distinct concat(PARTITION_METHOD,',',IFNULL(SUBPARTITION_METHOD,''))  from information_schema.partitions where table_schema='%v' and table_name='%v' and PARTITION_METHOD is not null) as `partitionType`,count(1) AS `sum` FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = '%v' AND TABLE_NAME = '%v' and PARTITION_METHOD is not null", s.TableInfo.Schema, s.TableInfo.BackendTableName, s.TableInfo.Schema, s.TableInfo.BackendTableName)
	if _, res, err = ExecSelectSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		SelectSqlStr: strSql,
		Expect:       ETL.Map,
	}); err != nil || res == nil {
		if err != nil {
			err = errors.New(fmt.Sprintf("%v %v", event, err))
		}
		return
	}
	if v1, ok := res.(map[string]any)["partitionType"]; ok {
		partitionData.Type = fmt.Sprintf("%v", v1)
	}
	if v1, ok := res.(map[string]any)["sum"]; ok {
		if len(fmt.Sprintf("%v", v1)) > 0 {
			if v2, err1 := strconv.ParseInt(fmt.Sprintf("%v", v1), 10, 64); err1 != nil {
				err = errors.New(fmt.Sprintf("%v table %v.%v partition sum conversion int failed. data is %v, error is %v", event, s.TableInfo.Schema, s.TableInfo.Table, res, err))
				return partitionData, err
			} else {
				partitionData.Sum = v2
			}
		}
	}
	if len(partitionData.Type) > 0 && partitionData.Sum > 0 {
		return partitionData, nil
	}
	return
}

func (my TableInfoMeta) partColumnActive(columnName []string, columnMeta []global.TableMeta) global.PartColumnMeta {
	var pc global.PartColumnMeta
	for _, v := range columnName {
		for _, p := range columnMeta {
			if strings.EqualFold(v, p.ColumnName) {
				pc.Type = append(pc.Type, p.ConvertColumnType.Type)
				pc.CharSet = append(pc.CharSet, p.Charset)
				pc.Collation = append(pc.Collation, p.CollationName)
			}
		}
	}
	return pc
}

type partitionBaseMeta struct {
	columnMeta       []global.TableMeta
	partitionMeta    []global.GetPartitionMeta
	tableDistributed []global.GetTableDistributed
}

func (my TableInfoMeta) partitionBaseMetaActive(s global.TablesMetaInfoInput) (p partitionBaseMeta, err error) {
	var (
		//Event     = "[mysqlPartitionBaseMetaActive]"
		f1        any
		allResult global.AllReturnResult
		tc        TableColumn
	)
	//获取表的列信息
	if allResult, err = tc.All(global.GetColumnTypeInput{
		LogSeq: s.LogSeq,
		DB:     s.DB,
		TableInfo: global.TableInfo{
			Schema: s.TableInfo.Schema,
			Table:  s.TableInfo.Table,
		},
	}); err != nil {
		return
	} else {
		p.columnMeta = allResult.Result
	}
	//获取表的分区信息
	if f1, err = my.GetPartitionMeta(s); err != nil {
		return
	}
	if f1 != nil && len(f1.([]global.GetPartitionMeta)) > 0 {
		p.partitionMeta = f1.([]global.GetPartitionMeta)
	}
	//获取表分布信息
	if f1, err = my.TableDistributed(s); err != nil {
		return
	}
	if f1 != nil && len(f1.([]global.GetTableDistributed)) > 0 {
		p.tableDistributed = f1.([]global.GetTableDistributed)
	}
	return
}

// FirstSubPartNameMarge 合并first Sub partition name
func (my TableInfoMeta) FirstSubPartNameMarge(s global.TablesMetaInfoInput) (q any, err error) {
	var (
		partitionShardMap   = make(map[string]global.BackendTable)
		PartMeth            string
		PartColumn          global.ManyLevelPartColumn
		partitionNameGarth  []string
		FirstNameMapSubName = make(map[string][]string)
		p                   partitionBaseMeta
		//Event               = "[mysqlFirstSubPartNameMarge]"
	)
	if p, err = my.partitionBaseMetaActive(s); err != nil {
		return
	}
	for _, j := range p.tableDistributed {
		var key = j.FirstPartitionName
		if len(j.SubPartitionName) > 0 && j.SubPartitionName != "NULL" && j.SubPartitionName != "''" {
			key = fmt.Sprintf("%v#%v", key, j.SubPartitionName)
		}
		if strings.EqualFold(j.DistributeMode, "NORMAL") {
			key = "single"
		}
		partitionShardMap[key] = global.BackendTable{
			FirstPartitionName: j.FirstPartitionName,
			SubPartitionName:   j.SubPartitionName,
			ShardName:          j.ShardName,
			BackendTableName:   j.BackendTableName,
		}
	}
	//normal表
	if len(p.partitionMeta) == 0 {
		return global.FirstSubPartNameMargeData{
			PartMeth:              "single",
			FirstNameMapSubName:   FirstNameMapSubName,
			PartColumn:            PartColumn,
			PartitionNameGarth:    []string{"single"},
			PartitionBackendShard: partitionShardMap,
		}, nil
	}
	for _, v := range p.partitionMeta {
		//分区方法
		if len(PartMeth) == 0 {
			//分区方法合并
			PartMeth = v.FirstPartMeth
			if len(v.SubPartMeth) > 0 && v.SubPartMeth != "NULL" && v.SubPartMeth != "''" {
				PartMeth = fmt.Sprintf("%v_%v", PartMeth, v.SubPartMeth)
			}
			//分区列信息
			PartColumn.FirstPar = my.partColumnActive(v.FirstPartColumn, p.columnMeta)
			if len(v.SubPartColumn) > 0 {
				PartColumn.SubPar = my.partColumnActive(v.SubPartColumn, p.columnMeta)
			}
		}
		//分区名合并
		var key = v.FirstPartName
		if len(v.SubPartName) > 0 && v.SubPartName != "NULL" && v.SubPartName != "''" {
			key = fmt.Sprintf("%v#%v", key, v.SubPartName)
		}
		partitionNameGarth = append(partitionNameGarth, key)
		if _, ok := FirstNameMapSubName[v.FirstPartName]; !ok {
			if len(v.SubPartName) > 0 {
				FirstNameMapSubName[v.FirstPartName] = []string{v.SubPartName}
			} else {
				FirstNameMapSubName[v.FirstPartName] = []string{}
			}
		} else {
			if len(v.SubPartName) > 0 && v.SubPartName != "NULL" && v.SubPartName != "''" {
				FirstNameMapSubName[v.FirstPartName] = append(FirstNameMapSubName[v.FirstPartName], v.SubPartName)
			}
		}
	}
	return global.FirstSubPartNameMargeData{
		PartMeth:              PartMeth,
		FirstNameMapSubName:   FirstNameMapSubName,
		PartColumn:            PartColumn,
		PartitionNameGarth:    partitionNameGarth,
		PartitionBackendShard: partitionShardMap,
	}, nil
}

//disPartRules 处理每个分区的分区规则
/*
	处理如下：
	1：拆分分区规则中是否用到分区函数
	2：合并分区规则中多个值现象（主要是list集合）
	传参：每个分区数据
	返值：分区规则
*/
func (my TableInfoMeta) disPartRules(meth string, s any) (r string) {
	switch meth {
	case "RANGE":
		p := fmt.Sprintf("%v", s)
		if strings.HasPrefix(strings.ToUpper(p), "TO_DATE") {

		}
		r = p
	case "LIST":
		l := fmt.Sprintf("%v", s)
		if len(l) == 0 {
			r = fmt.Sprintf("%v", s)
			return
		}
		if strings.Contains(l, ",") {
			var p []string
			for _, v := range strings.Split(l, ",") {
				p = append(p, strings.TrimSpace(v))
			}
			r = strings.Join(p, ",")
		}
	default:
		r = fmt.Sprintf("%v", s)
	}
	return
}
func (my TableInfoMeta) GetPartitionMeta(s global.TablesMetaInfoInput) (res any, err error) {
	var (
		event = "[MySQLGetPartitionMeta]"
		tpc   []global.GetPartitionMeta
	)
	strSql := fmt.Sprintf("SELECT `TABLE_SCHEMA` AS `schema`,`TABLE_NAME` AS `table`,`PARTITION_NAME` AS `firstPartName`,`SUBPARTITION_NAME` AS `subPartName`,`PARTITION_ORDINAL_POSITION` AS `firstPartSeq`,`SUBPARTITION_ORDINAL_POSITION` AS `subPartSeq`,`PARTITION_METHOD` AS `firstPartMeth`,`SUBPARTITION_METHOD` AS `subPartMeth`,`PARTITION_EXPRESSION` AS `firstPartColumn`,`SUBPARTITION_EXPRESSION` AS `subPartColumn`,`PARTITION_DESCRIPTION` as `partitionRules`,`PARTITION_COMMENT` as `partComment`  FROM `information_schema`.`PARTITIONS` WHERE `table_schema` = '%v' AND `table_name` = '%v'  AND `PARTITION_NAME` IS NOT NULL ORDER BY `PARTITION_ORDINAL_POSITION`,`SUBPARTITION_ORDINAL_POSITION`", s.TableInfo.Schema, s.TableInfo.Table)
	if _, res, err = ExecSelectSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		SelectSqlStr: strSql,
		Expect:       ETL.SMap,
	}); err != nil || res == nil {
		if err != nil {
			err = errors.New(fmt.Sprintf("%v %v", event, err))
		}
		return
	}
	var partitionResult []map[string]any
	for _, v := range res.([]map[string]any) {
		partitionResult = append(partitionResult, mapNullActive(v))
	}
	var columnFunc = map[string]string{"firstPartColumn": "firstPartColumnFunc", "subPartColumn": "subPartColumnFunc"}
	for _, v := range partitionResult {
		u := v
		//处理partition meth
		if w, ok := v["firstPartMeth"]; ok {
			meth := strings.ToUpper(fmt.Sprintf("%v", w))
			if strings.Contains(meth, "COLUMNS") {
				meth = strings.TrimSpace(strings.ReplaceAll(meth, "COLUMNS", ""))
			}
			u["firstPartMeth"] = meth
		}
		//处理partition meth
		if w, ok := v["partitionRules"]; ok {
			u["partitionRules"] = my.disPartRules(u["firstPartMeth"].(string), w)
		}
		//处理first sub partition column and first sub partition func
		for _, z := range []string{"firstPartColumn", "subPartColumn"} {
			if w, ok := v[z]; ok {
				meth := strings.ToUpper(fmt.Sprintf("%v", w))
				var y, t []string
				for _, x := range strings.Split(strings.ReplaceAll(meth, "`", ""), ",") {
					if strings.Contains(x, "(") && strings.Contains(x, ")") {
						var funcName, columnName string
						var left, right int
						if left = strings.Index(x, "("); left != -1 {
							funcName = x[:left]
						}
						if right = strings.Index(x, ")"); right != -1 {
							columnName = x[left+1 : right]
						}
						t = append(t, strings.TrimSpace(funcName))
						y = append(y, strings.TrimSpace(columnName))
					} else {
						y = append(y, strings.TrimSpace(x))
					}

				}
				u[z] = y
				u[columnFunc[z]] = t
			}
		}
		v1 := global.GetPartitionMeta{}
		// 将 map 转换为 JSON 字节流
		var jsonData []byte
		jsonData, err = json.Marshal(v)
		if err != nil {
			err = errors.New(fmt.Sprintf("%v []map[string]any strconv json fail. Execution process{strconv data:%v error:%v}", event, v, err))
			return nil, err
		}
		// 将 JSON 字节流反序列化为结构体
		err = json.Unmarshal(jsonData, &v1)
		if err != nil {
			err = errors.New(fmt.Sprintf("%v []byte json unmarshal struct fail. Execution process{strconv data:%v error:%v}", event, string(jsonData), err))
			return nil, err
		}
		v1.FirstPartColumn = columnNameRemoveQuotes(v1.FirstPartColumn)
		v1.SubPartColumn = columnNameRemoveQuotes(v1.SubPartColumn)
		tpc = append(tpc, v1)
	}
	return tpc, nil
}

func (my TableInfoMeta) PartitionConfig(s global.TablesMetaInfoInput) (any, error) {
	var (
		//Event               = "[mysqlPartitionConfig]"
		f1                  any
		tc                  TableColumn
		columnMeta          []global.TableMeta
		PartitionMeta       []global.GetPartitionMeta
		TableDistributed    []global.GetTableDistributed
		tpc                 []global.TablePartitionConfig
		firstPartitionMarge = make(map[string]int)
		firstPartitionGarth []global.SubPartitionMeta
		subPartitionMarge   = make(map[string][]global.SubPartitionMeta)
		err                 error
	)
	//获取表的列信息
	if f1, err = tc.All(global.GetColumnTypeInput{
		LogSeq: s.LogSeq,
		DB:     s.DB,
		TableInfo: global.TableInfo{
			SchemaTableGather: map[string]int{fmt.Sprintf("%v%v%v", s.TableInfo.Schema, global.SchemaTableSplit, s.TableInfo.Table): 1},
			Schema:            s.TableInfo.Schema,
			Table:             s.TableInfo.Table,
		},
	}); err != nil || f1 == nil {
		return nil, err
	}
	columnMeta = f1.(global.AllReturnResult).Result
	//获取表的分区信息
	if f1, err = my.GetPartitionMeta(s); err != nil || f1 == nil {
		return nil, err
	}
	PartitionMeta = f1.([]global.GetPartitionMeta)
	//获取表分布信息
	if f1, err = my.TableDistributed(s); err != nil || f1 == nil {
		return nil, err
	}
	TableDistributed = f1.([]global.GetTableDistributed)
	var (
		getColumnType = func(x []string, o []global.TableMeta) (z []string) {
			for _, v := range o {
				for _, y := range x {
					if strings.EqualFold(v.ColumnName, y) {
						z = append(z, v.ColumnType)
					}
				}
			}
			return
		}
	)
	if len(PartitionMeta) == 0 {
		tpc = append(tpc, global.TablePartitionConfig{
			Schema:      s.TableInfo.Schema,
			Table:       s.TableInfo.Table,
			TableObject: "single",
			FirstPartition: global.SubPartitionMeta{
				Name: "single",
			},
			BackendTableName: s.TableInfo.Table,
			ShardName:        TableDistributed[0].ShardName,
		})
		return tpc, nil
	}
	for _, v := range PartitionMeta {
		if _, ok := firstPartitionMarge[v.FirstPartName]; !ok {
			firstPartitionGarth = append(firstPartitionGarth, global.SubPartitionMeta{
				Sum:        strconv.Itoa(len(PartitionMeta)),
				Func:       v.FirstPartColumnFunc,
				Name:       v.FirstPartName,
				Meth:       v.FirstPartMeth,
				ColumnName: v.FirstPartColumn,
				ColumnType: getColumnType(v.FirstPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.FirstPartSeq,
			})
			subPartitionMarge[v.FirstPartName] = []global.SubPartitionMeta{{
				Func:       v.SubPartColumnFunc,
				Name:       v.SubPartName,
				Meth:       v.SubPartMeth,
				ColumnName: v.SubPartColumn,
				ColumnType: getColumnType(v.SubPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.SubPartSeq,
			}}
			firstPartitionMarge[v.FirstPartName]++
		} else {
			subPartitionMarge[v.FirstPartName] = append(subPartitionMarge[v.FirstPartName], global.SubPartitionMeta{
				Func:       v.SubPartColumnFunc,
				Name:       v.SubPartName,
				Meth:       v.SubPartMeth,
				ColumnName: v.SubPartColumn,
				ColumnType: getColumnType(v.SubPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.SubPartSeq,
			})
		}
	}
	for _, v := range firstPartitionGarth {
		var subPartGarth []global.SubPartitionMeta
		var subPartMeth string
		if w, ok := subPartitionMarge[v.Name]; ok {
			subPartGarth = w
		}
		if len(subPartGarth) > 0 {
			subPartMeth = subPartGarth[0].Meth
		}
		tpc = append(tpc, global.TablePartitionConfig{
			Schema:           s.TableInfo.Schema,
			Table:            s.TableInfo.Table,
			FirstPartition:   v,
			SubPartMeth:      subPartMeth,
			SubPartMeta:      subPartGarth,
			ShardName:        "single",
			TableObject:      "partition",
			BackendTableName: s.TableInfo.Table,
		})
	}
	return tpc, nil
}

// -------------------new partition
func colGetParameter(s global.TablesMetaInfoInput) global.GetColumnTypeInput {
	return global.GetColumnTypeInput{
		LogSeq: s.LogSeq,
		DB:     s.DB,
		TableInfo: global.TableInfo{
			SchemaTableGather: map[string]int{fmt.Sprintf("%v%v%v", s.TableInfo.Schema, global.SchemaTableSplit, s.TableInfo.Table): 1},
			Schema:            s.TableInfo.Schema,
			Table:             s.TableInfo.Table,
		},
		Input: s.Input,
	}
}
func normalShardActive(p partMethBaseResultS) (res []global.PartShardS) {
	res = append(res, global.PartShardS{
		PartName:         "SINGLE",
		Mode:             "NORMAL",
		ShardName:        "SINGLE",
		BackendTableName: p.ColMetaResult.Result[0].TableName,
	})
	return
}
func partShardActive(p partMethBaseResultS) (res []global.PartShardS, partName []string) {
	resOld := normalShardActive(p)
	partName = []string{"single"}
	if len(p.BaseData) == 0 { //普通表
		res = resOld
		return
	}
	partName = []string{}
	for _, x := range p.BaseData {
		partName = append(partName, x.FirPartName)
		res = append(res, global.PartShardS{
			Mode:             "NORMAL",
			PartName:         x.FirPartName,
			ShardName:        "single",
			BackendTableName: resOld[0].BackendTableName,
		})
	}
	return
}
func getPartMetaBaseData(s global.TablesMetaInfoInput) (res []partMetaBaseDataS, err error) {
	var (
		f1    any
		event = "[MySQLGetPartMetaBaseData]"
	)
	strSql := fmt.Sprintf("SELECT \n"+
		"`TABLE_SCHEMA` AS `schema`, \n"+
		"`TABLE_NAME` AS `table`, \n"+
		"`PARTITION_NAME` AS `firstPartName`, \n"+
		"`SUBPARTITION_NAME` AS `subPartName`, \n"+
		"`PARTITION_ORDINAL_POSITION` AS `firstPartSeq`, \n"+
		"`SUBPARTITION_ORDINAL_POSITION` AS `subPartSeq`, \n"+
		"`PARTITION_METHOD` AS `firstPartMeth`, \n"+
		"`SUBPARTITION_METHOD` AS `subPartMeth`, \n"+
		"`PARTITION_EXPRESSION` AS `firstPartColumn`, \n"+
		"`SUBPARTITION_EXPRESSION` AS `subPartColumn`, \n"+
		"`PARTITION_DESCRIPTION` as `partitionRules`, \n"+
		"`PARTITION_COMMENT` as `partComment`  \n"+
		"FROM `information_schema`.`PARTITIONS` \n"+
		"WHERE `table_schema` = '%v' AND `table_name` = '%v'  \n"+
		"AND `PARTITION_NAME` IS NOT NULL \n"+
		"ORDER BY `PARTITION_ORDINAL_POSITION`,`SUBPARTITION_ORDINAL_POSITION`", s.TableInfo.Schema, s.TableInfo.Table)
	if _, f1, err = ExecSelectSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		SelectSqlStr: strSql,
		Expect:       ETL.SMap,
	}); err != nil || f1 == nil {
		if err != nil {
			err = errors.New(fmt.Sprintf("%v %v", event, err))
		}
		return nil, err
	}
	for _, v := range f1.([]map[string]any) {
		var ps partMetaBaseDataS
		v = mapNullActive(v)
		if err = global.MapSubToStructSubJsonName(mapNullActive(v), &ps); err != nil {
			return
		}
		res = append(res, ps)
	}
	return
}
func basePartColumnSpit(s string) (res []string) {
	for _, v := range strings.Split(s, ",") {
		res = append(res, strings.ReplaceAll(v, "`", ""))
	}
	return
}

func partMetaBaseInit(s global.TablesMetaInfoInput) (res partMethBaseResultS, err error) {
	var (
		c TableColumn
	)
	if res.BaseData, err = getPartMetaBaseData(s); err != nil {
		return
	}
	if res.ColMetaResult, err = c.All(colGetParameter(s)); err != nil {
		return
	}
	if len(res.BaseData) > 0 {
		res.FirPartColumnNameResult = basePartColumnSpit(res.BaseData[0].FirPartColumn)
		res.SubPartColumnNameResult = basePartColumnSpit(res.BaseData[0].SubPartColumn)
	}
	return
}
func partColumnActive(p partMethBaseResultS) (res global.PartDefinitionS) {
	if len(p.BaseData) == 0 {
		return
	}
	for _, v := range p.ColMetaResult.Result {
		for _, x := range p.FirPartColumnNameResult {
			if strings.EqualFold(x, v.ColumnName) {
				res.PartColS.FirPartCol = append(res.PartColS.FirPartCol, v.ColumnName)
				res.PartColT.FirPartColType = append(res.PartColT.FirPartColType, v.ColumnType)
				res.PartColC.FirPartColChart = append(res.PartColC.FirPartColChart, v.Charset)
				res.PartColC.FirPartColCollation = append(res.PartColC.FirPartColCollation, v.CollationName)
			}
		}
		for _, x := range p.SubPartColumnNameResult {
			if strings.EqualFold(x, v.ColumnName) {
				res.PartColS.SubPartCol = append(res.PartColS.SubPartCol, v.ColumnName)
				res.PartColT.SubPartColType = append(res.PartColT.SubPartColType, v.ColumnType)
				res.PartColC.SubPartColChart = append(res.PartColC.SubPartColChart, v.Charset)
				res.PartColC.SubPartColCollation = append(res.PartColC.SubPartColCollation, v.CollationName)
			}
		}
	}
	return
}
func (my TableInfoMeta) GetPartMeta(s global.TablesMetaInfoInput) (res global.PartMetaResultS, err error) {
	var (
		baseResult partMethBaseResultS
	)
	if baseResult, err = partMetaBaseInit(s); err != nil {
		return
	}
	res.Shard, res.NameGarth = partShardActive(baseResult)
	res.Definition = partColumnActive(baseResult)
	if len(res.NameGarth) >= 1 {
		if !strings.EqualFold(res.NameGarth[0], "single") {
			res.Overview.Status = true
		}
	}
	return
}
